#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <unistd.h>

#include <libpmemobj++/make_persistent.hpp>
#include <libpmemobj++/p.hpp>
#include <libpmemobj++/persistent_ptr.hpp>
#include <libpmemobj++/pool.hpp>
#include <libpmemobj++/transaction.hpp>

enum queue_op {
    PUSH,
    POP,
    SHOW,
    EXIT,
    MAX_OPS,
};

struct queue_node {
    pmem::obj::p<int> value;
    pmem::obj::persistent_ptr<queue_node> next;
};

struct queue {
    void push(pmem::obj::pool_base &pop, int value) {
        pmem::obj::transaction::run(pop, [&]{
            auto node = pmem::obj::make_persistent<queue_node>();
            node->value = value;
            node->next = nullptr;

            if(head == nullptr) {
                head = tail = node;
            } else {
                tail->next = node;
                tail = node;
            }
        });
    }

    int pop(pmem::obj::pool_base &pop) {
        int value;
        pmem::obj::transaction::run(pop, [&]{
            if (head == nullptr) {
                throw std::out_of_range("no element");
            }
            auto head_ptr = head;
            value = head->value;

            head = head->next;
            pmem::obj::delete_persistent<queue_node>(head_ptr);

            if(head == nullptr) {
                tail = nullptr;
            }
        });
        return value;
    }

    void show() {
        auto node = head;
        while (node != nullptr) {
            std::cout << "show: " << node->value << std::endl;
            node = node->next;
        }
        std::cout << std::endl;
    }

private:
    pmem::obj::persistent_ptr<queue_node> head = nullptr;
    pmem::obj::persistent_ptr<queue_node> tail = nullptr;


};

const char *ops_str[MAX_OPS] = {"push", "pop", "show", "exit"};
queue_op parse_queue_ops(const std::string &ops)
{
    
    for (int i = 0; i < MAX_OPS; i++) {
        if (ops == ops_str[i]) {
            return (queue_op)i;
        }
    }
    return MAX_OPS;
}
static int file_exists(char const *file) {
    return access(file, F_OK);
}
int main(int argc, char * argv[]) {
    if (argc < 2) {
        std::cerr << "usage: " << argv[0] << " pool" << std::endl;
        return 1;
    }
    auto path = argv[1];
    
    pmem::obj::pool<queue> pool;
    if (file_exists(path) != 0) {
        pool = pmem::obj::pool<queue>::create(path, "queue", PMEMOBJ_MIN_POOL);
    } else {
        pool = pmem::obj::pool<queue>::open(path, "queue");
    }

    
    auto q = pool.root();

    while (1) {
        std::cout << "[push value | pop | show | exit]" << std::endl;
        std::string command;
        std::cin >> command;
        auto ops = parse_queue_ops(std::string(command));
        switch (ops) {
            case PUSH: {
                int value;
                std::cin >> value;
                q->push(pool, value);
                break;
            }
            case POP: {
                std::cout << q->pop(pool) <<std::endl;
                break;
            }
            case SHOW: {
                q->show();
                break;
            }
            case EXIT: {
                pool.close();
                exit(0);
            }
            default: {
                std::cerr << "unknown ops" << std::endl;
                pool.close();
                exit(0);
            }
        }
    }
}

/*
g++ pmemobjcpp_queue.cpp -lpmemobj -lpmem
*/